package org.wang.excelfromtobean.common.util;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.NumberToTextConverter;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.wang.excelfromtobean.common.annotation.ExcelCellMapping;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_BOOLEAN;
import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_ERROR;
import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_FORMULA;
import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_NUMERIC;
import static org.apache.poi.ss.usermodel.Cell.CELL_TYPE_STRING;

/**
 * TODO: 该方法用于excel 复杂表单提取 ，其他的简单的列表可以采用easypoi的方法 获取
 * Excel to object conversion utility.
 *
 * @author wangchong
 */
public class ExcelToObjectUtil {

    /**
     * 从Excel文件中转换出指定类的对象。
     *
     * @param clazz 需要转换成的对象的类。
     * @param excelFilePath Excel文件的路径。
     * @param configFilePath 配置文件的路径，用于映射Excel列到对象字段。
     * @param sheetNum 工作表的编号，从0开始。
     * @return 转换后的对象。
     * @throws IOException 如果读取文件时发生错误。
     * @throws IllegalAccessException 如果访问字段时发生错误。
     * @throws InstantiationException 如果实例化对象时发生错误。
     * @throws ParseException 如果解析日期格式时发生错误。
     */
    /**
     * Converts an Excel file to an object of the specified class.
     */
    public static <T> T convert(Class<T> clazz, String excelFilePath, String configFilePath, Integer sheetNum) throws IOException, IllegalAccessException, InstantiationException, ParseException {
        Map<String, Map<String, String>> config = readConfig(configFilePath);
        Workbook workbook = getWorkbookInstance(excelFilePath);
        return mapFromExcel(clazz, workbook, config, sheetNum);
    }


    /**
     * 从Excel文件中转换出指定类的Map。
     *
     * @param excelFilePath Excel文件的路径。
     * @param configFilePath 配置文件的路径，用于映射Excel列到对象字段。
     * @param sheetNum 工作表的编号，从0开始。
     * @return 转换后的对象。
     * @throws IOException 如果读取文件时发生错误。
     * @throws IllegalAccessException 如果访问字段时发生错误。
     * @throws InstantiationException 如果实例化对象时发生错误。
     * @throws ParseException 如果解析日期格式时发生错误。
     */
    /**
     * Converts an Excel file to an object of the specified class.
     */
    public static Map<String,Object> convert(String excelFilePath, String configFilePath, Integer sheetNum) throws IOException, IllegalAccessException, InstantiationException, ParseException {
        Map<String, Map<String, String>> config = readConfig(configFilePath);
        Workbook workbook = getWorkbookInstance(excelFilePath);
        return mapFromExcel(workbook, config, sheetNum);
    }


    /**
     * 根据文件路径获取Workbook实例，支持.xls和.xlsx格式。
     *
     * @param filePath Excel文件的路径。
     * @return Workbook实例。
     * @throws IOException 如果读取文件时发生错误。
     */
    public static Workbook getWorkbookInstance(String filePath) throws IOException {
        Workbook workbook;
        if (filePath.toLowerCase().endsWith(".xls")) {
            workbook = new HSSFWorkbook(new FileInputStream(filePath));
        } else if (filePath.toLowerCase().endsWith(".xlsx")) {
            workbook = new XSSFWorkbook(filePath);
        } else {
            throw new IllegalArgumentException("Unsupported file format. Only .xls and .xlsx files are supported.");
        }
        return workbook;
    }

    /**
     * 读取配置文件，将其解析为Map。
     *
     * @param configFilePath 配置文件的路径。
     * @return 配置信息的Map。
     * @throws IOException 如果读取文件时发生错误。
     */
    private static Map<String, Map<String, String>> readConfig(String configFilePath) throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();

        // 使用TypeReference替代JavaType进行类型转换，这种方式更为简洁且不易出错
        TypeReference<Map<String, Map<String, String>>> typeRef = new TypeReference<Map<String, Map<String, String>>>() {
        };

        // 从文件路径读取JSON内容并转换为Map类型
        File configFile = new File(configFilePath);
        Map<String, Map<String, String>> config = objectMapper.readValue(configFile, typeRef);

        return config;
    }

    /**
     * 使用反射和配置信息将Excel数据映射到对象。
     *
     * @param clazz    需要转换成的对象的类。
     * @param workbook Excel数据的Workbook实例。
     * @param config   映射配置信息。
     * @param sheetNum 工作表的编号，从0开始。
     * @return 转换后的对象。
     * @throws IOException            如果读取Excel数据时发生错误。
     * @throws IllegalAccessException 如果访问字段时发生错误。
     * @throws InstantiationException 如果实例化对象时发生错误。
     * @throws ParseException         如果解析日期格式时发生错误。
     */
    private static <T> T mapFromExcel(Class<T> clazz, Workbook workbook, Map<String, Map<String, String>> config, Integer sheetNum) throws IOException, IllegalAccessException, InstantiationException, ParseException {
        T instance = clazz.newInstance();
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            ExcelCellMapping annotation = field.getAnnotation(ExcelCellMapping.class);
            if (annotation != null) {
                /**走实体对象*/
                processFieldWithAnnotation(instance, field, workbook, annotation, sheetNum);
            } else {
                /**走json模板*/
                processFieldWithoutAnnotation(instance, field, workbook, config, sheetNum);
            }
        }

        return instance;
    }



    /**
     * 使用反射和配置信息将Excel数据映射到对象。
     *
     * @param workbook Excel数据的Workbook实例。
     * @param config   映射配置信息。
     * @param sheetNum 工作表的编号，从0开始。
     * @return 转换后的对象。
     * @throws IOException            如果读取Excel数据时发生错误。
     * @throws IllegalAccessException 如果访问字段时发生错误。
     * @throws InstantiationException 如果实例化对象时发生错误。
     * @throws ParseException         如果解析日期格式时发生错误。
     */
    private static Map<String, Object> mapFromExcel(Workbook workbook, Map<String, Map<String, String>> config, Integer sheetNum) throws IOException, IllegalAccessException, ParseException {
        /**走json模板*/
        return processFieldWithoutAnnotation(workbook, config, sheetNum);
    }

    /**
     * 处理带有ExcelCellMapping注解的字段。
     *
     * @param instance   目标对象实例。
     * @param field      需要处理的字段。
     * @param workbook   Excel数据的Workbook实例。
     * @param annotation 字段上的ExcelCellMapping注解。
     * @param sheetNum   工作表的编号，从0开始。
     * @throws IOException            如果读取Excel数据时发生错误。
     * @throws IllegalAccessException 如果访问字段时发生错误。
     * @throws ParseException         如果解析日期格式时发生错误。
     */
    private static void processFieldWithAnnotation(Object instance, Field field, Workbook workbook, ExcelCellMapping annotation, Integer sheetNum) throws IOException, IllegalAccessException, ParseException {
        String columnRef = annotation.column();
        Object value = readCellValue(workbook.getSheetAt(sheetNum), columnRef, field.getType(), annotation.dateFormat());
        field.setAccessible(true);
        if (!StrUtil.isEmptyIfStr(annotation.code())) {
            /**走字典缓存 需要连库,  缓存采用本地缓存 ehcache  + 数据库 查询的方式 ，这样 高效快捷*/
//            value = DictCache.getDictKey(annotation.code(), Convert.toStr(value));
        }
        field.set(instance, value);
    }

    /**
     * 处理没有ExcelCellMapping注解的字段。
     *
     * @param instance 目标对象实例。
     * @param field    需要处理的字段。
     * @param workbook Excel数据的Workbook实例。
     * @param config   映射配置信息。
     * @param sheetNum 工作表的编号，从0开始。
     * @throws IOException            如果读取Excel数据时发生错误。
     * @throws IllegalAccessException 如果访问字段时发生错误。
     * @throws ParseException         如果解析日期格式时发生错误。
     */
    private static void processFieldWithoutAnnotation(Object instance, Field field, Workbook workbook, Map<String, Map<String, String>> config, Integer sheetNum) throws IOException, IllegalAccessException, ParseException {
        Map<String, String> fieldConfig = config.getOrDefault(field.getName(), new HashMap<>());
        String columnRef = fieldConfig.get("column");
        String code = fieldConfig.get("code");
        String delimiter = fieldConfig.get("delimiter");
        Integer index = Convert.toInt(fieldConfig.get("index"));
        if (columnRef != null) {
            String dateFormat = fieldConfig.get("dateFormat");
            Object value = readCellValue(workbook.getSheetAt(sheetNum), columnRef, field.getType(), dateFormat);
            field.setAccessible(true);
            value = extractByDelimiter(value, delimiter, index);
            if (!StrUtil.isEmptyIfStr(code)) {
                /**走字典缓存 需要连库,  缓存采用本地缓存 ehcache  + 数据库 查询的方式 ，这样 高效快捷*/
//                value = DictCache.getDictKey(code, Convert.toStr(value));
            }
            field.set(instance, value);
        }
    }


    /**
     * 处理没有ExcelCellMapping注解的字段。
     *
     * @param workbook Excel数据的Workbook实例。
     * @param config   映射配置信息。
     * @param sheetNum 工作表的编号，从0开始。
     * @throws IOException            如果读取Excel数据时发生错误。
     * @throws IllegalAccessException 如果访问字段时发生错误。
     * @throws ParseException         如果解析日期格式时发生错误。
     */
    private static Map<String,Object> processFieldWithoutAnnotation(Workbook workbook, Map<String, Map<String, String>> config, Integer sheetNum) throws IOException, IllegalAccessException, ParseException {
       Map<String,Object> result = new HashMap<>();
        for (String outerKey : config.keySet()) {
            /**将外层的 key 作为 Map 的key 里面的 属性 赋值*/
            Map<String, String> fieldConfig = config.get(outerKey);
            String columnRef = fieldConfig.get("column");
            String code = fieldConfig.get("code");
            String delimiter = fieldConfig.get("delimiter");
            Integer index = Convert.toInt(fieldConfig.get("index"));
            if (columnRef != null) {
                String dateFormat = fieldConfig.get("dateFormat");
                Object value = readCellValue(workbook.getSheetAt(sheetNum), columnRef, null, dateFormat);
                value = extractByDelimiter(value, delimiter, index);
                if (!StrUtil.isEmptyIfStr(code)) {
                    /**走字典缓存 需要连库,  缓存采用本地缓存 ehcache  + 数据库 查询的方式 ，这样 高效快捷*/
//                    value = DictCache.getDictKey(code, Convert.toStr(value));
                }
                result.put(outerKey,value);
            }
        }
        return result;
    }


    /**
     * 从指定的单元格读取值，并根据字段类型和日期格式进行转换。
     *
     * @param sheet      工作表。
     * @param columnRef  单元格的列参考（如"A1"）。
     * @param fieldType  字段类型。
     * @param dateFormat 日期格式。
     * @return 转换后的值。
     * @throws ParseException 如果解析日期格式时发生错误。
     */
    private static Object readCellValue(Sheet sheet, String columnRef, Class<?> fieldType, String dateFormat) throws ParseException {
        // 解析列参考，假设格式为"A1"，其中A是列字母，1是行号
        char columnLetter = columnRef.charAt(0);
        int rowIndex = Integer.parseInt(columnRef.substring(1)) - 1; // Excel行号从1开始，所以减1
        int columnIndex = columnLetter - 'A'; // 直接计算列索引，不包含行号部分

        Row row = sheet.getRow(rowIndex);
        if (StrUtil.isEmptyIfStr(row)) {
            return null;
        }

        Cell cell = row.getCell(columnIndex);
        if (StrUtil.isEmptyIfStr(cell)) {
            return null;
        }

        switch (cell.getCellType()) {
            case CELL_TYPE_STRING:
                return cell.getStringCellValue();
            case CELL_TYPE_NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date dateCellValue = cell.getDateCellValue();
                    // 根据需求调整日期格式化器
                    return getDateFormat(dateFormat).format(cell.getDateCellValue());
                } else {
                    return NumberToTextConverter.toText(cell.getNumericCellValue());
                }
            case CELL_TYPE_BOOLEAN:
                return cell.getBooleanCellValue();
            case CELL_TYPE_FORMULA: // 处理公式类型
                switch (cell.getCachedFormulaResultType()) {
                    case CELL_TYPE_NUMERIC:
                        return cell.getNumericCellValue();
                    case CELL_TYPE_STRING:
                        return cell.getStringCellValue();
                    case CELL_TYPE_BOOLEAN:
                        return cell.getBooleanCellValue();
                    case CELL_TYPE_ERROR:
                        return null; // 或者抛出异常，根据需求处理
                    default:
                        throw new UnsupportedOperationException("Unsupported cached formula result type: " + cell.getCachedFormulaResultType());
                }
            default:
                throw new UnsupportedOperationException("Unsupported cell type: " + cell.getCellType());
        }
    }

    private static SimpleDateFormat getDateFormat(String pattern) {
        // 线程局部变量存储 SimpleDateFormat 实例，避免多线程竞争
        ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
        SimpleDateFormat sdf = threadLocal.get();
        if (sdf == null) {
            sdf = new SimpleDateFormat(pattern != null ? pattern : "yyyy-MM-dd");
            threadLocal.set(sdf);
        }
        return sdf;
    }


    /**
     * 傻瓜式方法：根据分隔符从输入字符串中提取信息。
     *
     * @param value     输入的原始字符串。
     * @param delimiter 分隔符，用于界定要提取内容的边界。
     * @param index     提取第几个分隔后的部分，默认为第一个（索引0）。
     * @return 提取到的内容，如果索引超出范围则返回null。
     */
    public static Object extractByDelimiter(Object value, String delimiter, Integer index) {
        if (value instanceof String && !StrUtil.isEmptyIfStr(delimiter) && !StrUtil.isEmptyIfStr(delimiter)) {
            String input = Convert.toStr(value);
            /**分割字符串，确保不会丢失最后一个分隔符后面的空字符串*/
            String[] parts = input.split(delimiter);
            if (index > 0 && index <= parts.length) {
                /**返回指定索引的分段，并去除前后空白*/
                return parts[index - 1].trim();
            }
        }
        /**如果不是字符串或者没有配置，则直接返回原值*/
        return value;
    }


}